package com.qqt.csr.web.service;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qqt.csr.common.acl.aics.AICustomerServiceClient;
import com.qqt.csr.common.acl.aics.resp.ChannelInfoDTO;
import com.qqt.csr.common.acl.aics.resp.CsMemberInfoDTO;
import com.qqt.csr.common.exception.BizException;
import com.qqt.csr.common.exception.StatusCode;
import com.qqt.csr.common.utils.CacheKeyUtil;
import com.qqt.csr.common.utils.JsonUtil;
import com.qqt.csr.common.utils.ServiceAssert;
import com.qqt.csr.im.dto.BindResultDTO;
import com.qqt.csr.im.dto.ConversationDTO;
import com.qqt.csr.im.dto.SendSystemMessageDTO;
import com.qqt.csr.im.dto.req.CreatingConversationReqDTO;
import com.qqt.csr.im.dto.resp.CreatingConversationRespDTO;
import com.qqt.csr.im.entity.Account;
import com.qqt.csr.im.entity.Conversation;
import com.qqt.csr.im.entity.Room;
import com.qqt.csr.im.enums.ConversationStateEnum;
import com.qqt.csr.im.service.AccountService;
import com.qqt.csr.im.service.mongo.ConversationService;
import com.qqt.csr.im.service.mongo.MessageService;
import com.qqt.csr.route.dto.req.CreatingRouteTaskDTO;
import com.qqt.csr.route.dto.resp.CreatingRouteTaskRespDTO;
import com.qqt.csr.route.dto.resp.RouteResultDTO;
import com.qqt.csr.route.dto.resp.RouteTaskDTO;
import com.qqt.csr.route.enums.RouteTaskStatusEnum;
import com.qqt.csr.route.service.CsReceptionLimitService;
import com.qqt.csr.route.service.RouteTaskService;
import com.qqt.csr.web.dto.member.VisitorInfoDTO;
import com.qqt.csr.web.vo.req.AcceptingConversationReqVO;
import com.qqt.csr.web.vo.resp.VisitorConversationInfoRespVO;
import com.qqt.csr.workbench.entity.SkillSet;
import com.qqt.csr.workbench.entity.SkillSetConfig;
import com.qqt.csr.workbench.service.ChatTimeJobService;
import com.qqt.csr.workbench.service.SkillSetConfigService;
import com.qqt.csr.workbench.service.SkillSetService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AcceptingConversationService {
    @Autowired
    private RouteTaskService routeTaskService;
    @Autowired
    private AICustomerServiceClient aiCustomerServiceClient;
    @Autowired
    private AccountService accountService;
    @Autowired
    private ConversationService conversationService;
    @Autowired
    private CsReceptionLimitService csReceptionLimitService;
    @Autowired
    private ArchiveEventMqProducerService archiveEventMqProducerService;
    @Autowired
    private SkillSetService skillSetService;
    @Autowired
    private SkillSetConfigService skillSetConfigService;
    @Autowired
    private MessageService messageService;
    @Autowired
    private ClosingConversationService closingConversationService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ChatTimeJobService chatTimeJobService;
    @Autowired
    private ImAccountService imAccountService;

    /**
     * 访客转人工，接入会话
     *
     * @return
     */
    public VisitorConversationInfoRespVO accept(AcceptingConversationReqVO req) {
        // 通过渠道获取绑定的技能组相关信息
        ChannelInfoDTO channelInfo = getChannelInfo(req.getChannelId());

        // 访客
        VisitorInfoDTO visitorInfo = VisitorInfoDTO.of(req.getTenantId(), req.getChannelUserId(), channelInfo);
        visitorInfo.bindImAccount(accountService.getByAccountUserId(imAccountService.buildUserImUserId(visitorInfo)));
        ServiceAssert.notNull(visitorInfo.getAccount(), StatusCode.Common.NOT_EXIST.getCode(), "非法访客！");

        // 判断下当前访客是否已经有进行中会话，如果有，则直接返回，否则创建新会话
        VisitorConversationInfoRespVO existConversation = queryConversationIfExist(visitorInfo, channelInfo);
        if (existConversation != null) {
            return existConversation;
        }

        // 创建会话
        CreatingConversationRespDTO conversation = createConversation(channelInfo, visitorInfo);
        ServiceAssert.notNull(conversation, StatusCode.Common.NULL_ERROR.getCode(), "创建会话失败");

        // 创建路由分发任务
        CreatingRouteTaskRespDTO routeResultDTO = createRouteTask(visitorInfo, channelInfo, conversation);
        if (routeResultDTO == null || RouteTaskStatusEnum.FAIL.equals(routeResultDTO.getStatus())) {
            conversationService.end(conversation.getConversationId());
            String errorTip = Optional.ofNullable(routeResultDTO).map(CreatingRouteTaskRespDTO::getContent).orElse("创建会话失败");
            return VisitorConversationInfoRespVO.buildFail(errorTip);
        }

        // 记录访客今日转人工次数
        recordVisitorAccessCsCount(visitorInfo.getAccount().getId());

        return VisitorConversationInfoRespVO.builder()
                .conversationId(conversation.getConversationId())
                .onlineCs(2)
                .build();
    }

    /**
     * 处理路由分发结果
     *
     * @param resultDTO
     */
    public void handleRouteResult(RouteResultDTO resultDTO) {
        RouteTaskDTO routeTaskDTO = resultDTO.getRouteTaskDTO();
        Long csId = Optional.ofNullable(resultDTO.getCsInfo()).map(CsMemberInfoDTO::getUserId).orElse(null);

        if (StringUtils.isNotBlank(resultDTO.getContent())) {
            // 如果有提示信息则回复到访客端
            JSONObject systemMsgExt = new JSONObject();
            systemMsgExt.put("systemMsgType", "2");
            String systemMsgExtStr = systemMsgExt.toJSONString();
            String imAccountUserId = imAccountService.buildUserImUserId(routeTaskDTO.getChannelId(), routeTaskDTO.getChannelUserId());
            SendSystemMessageDTO systemMessageDTO = SendSystemMessageDTO.of(imAccountUserId, routeTaskDTO.getConversationId(), resultDTO.getContent(), systemMsgExtStr, StringUtils.EMPTY);
            messageService.sendSystemMsg(systemMessageDTO, null);
        }

        if (RouteTaskStatusEnum.FAIL.equals(routeTaskDTO.getStatus())) {
            // 分配客服失败，关闭会话
            closingConversationService.closeConversation(routeTaskDTO.getConversationId(), csId);
            return;
        }
        if (!RouteTaskStatusEnum.ROUTED.equals(routeTaskDTO.getStatus())) {
            // 还在排队分配客服
            return;
        }

        SkillSet skillSet = skillSetService.queryById(resultDTO.getSkillSetDTO().getSkillSetId(), routeTaskDTO.getTenantId());
        ServiceAssert.notNull(skillSet, StatusCode.Common.NOT_EXIST.getCode(), "技能组不存在");
        SkillSetConfig skillSetConfig = skillSetConfigService.queryBySkillSetId(skillSet.getId());

        String visitorImAccountUserId = imAccountService.buildUserImUserId(routeTaskDTO.getChannelId(), routeTaskDTO.getChannelUserId());
        String csImAccountUserId = imAccountService.buildCsImUserId(csId);
        Map<String, Account> accountMap = accountService.getMapByUserIds(Lists.newArrayList(visitorImAccountUserId, csImAccountUserId));
        Account visitorAccount = accountMap.get(visitorImAccountUserId);
        Account csAccount = accountMap.get(csImAccountUserId);

        // 分配成功，将客服加入到会话房间中
        BindResultDTO bindResult = bindRoom(skillSet, resultDTO, accountMap);
        if (bindResult == null) {
            // 绑定会话失败
            log.info("绑定会话失败，关闭会话");
            closingConversationService.closeConversation(routeTaskDTO.getConversationId(), csId);
            return;
        }

        try {
            // 通知客服工作台，新会话接入通知
            pushNewConversationEventNotify(bindResult.getRoom(), csAccount, accountMap);
            // 创建会话定时监视任务
            chatTimeJobService.createMonitorJob(skillSetConfig.getId(), routeTaskDTO.getChannelUserId(), routeTaskDTO.getChannelId(), csId, bindResult.getConversation().getId());
            // 推送系统消息到访客和客服工作台
            pushCreateConversationSystemMsg(csAccount, visitorAccount, bindResult.getConversation(), skillSetConfig, resultDTO.getCsInfo());
            // 记录客服今日接待访客数量
            recordCsAccessVisitorCount(csId);
        } catch (Exception e) {
            log.error("将客服绑定到会话中，后续逻辑异常！会话id：{}", bindResult.getConversation().getId(), e);
        }
    }

    /**
     * 获取访客今日转人工次数
     *
     * @param visitorImAccountId
     * @return
     */
    public long getVisitorAccessCsCount(Long visitorImAccountId) {
        String redisKey = String.format(CacheKeyUtil.CsrRoute.VISITOR_ACCESS_CS_COUNT, visitorImAccountId);
        return redissonClient.getAtomicLong(redisKey).get();
    }

    /**
     * 获取客服今日接待访客数量
     *
     * @param csId
     * @return
     */
    public Long getCsAccessVisitorCount(Long csId) {
        String redisKey = String.format(CacheKeyUtil.CsrRoute.CS_ACCESS_VISITOR_COUNT, csId);
        return redissonClient.getAtomicLong(redisKey).get();
    }

    /**
     * 访客获取已存在未结束的会话
     *
     * @param req
     * @return
     */
    public VisitorConversationInfoRespVO getUnClosedConversation(AcceptingConversationReqVO req) {
        // 通过渠道获取绑定的技能组相关信息
        ChannelInfoDTO channelInfo = getChannelInfo(req.getChannelId());

        // 访客
        VisitorInfoDTO visitorInfo = VisitorInfoDTO.of(req.getTenantId(), req.getChannelUserId(), channelInfo);
        visitorInfo.bindImAccount(accountService.getByAccountUserId(imAccountService.buildUserImUserId(visitorInfo)));
        ServiceAssert.notNull(visitorInfo.getAccount(), StatusCode.Common.NOT_EXIST.getCode(), "非法访客！");

        // 判断下当前访客是否已经有进行中会话，如果有，则直接返回，否则创建新会话
        return queryConversationIfExist(visitorInfo, channelInfo);
    }


    /**
     * 获取渠道信息
     *
     * @param channelId
     * @return
     */
    private ChannelInfoDTO getChannelInfo(String channelId) {
        ChannelInfoDTO channelInfoDto = aiCustomerServiceClient.queryChannelInfo(channelId);
        ServiceAssert.notNull(channelInfoDto, StatusCode.Common.NULL_ERROR.getCode(), "渠道未配置");
        ServiceAssert.isTrue(channelInfoDto.getRouteId() != null && channelInfoDto.getRouteId() > 0, StatusCode.Common.NULL_ERROR.getCode(), "渠道未绑定路由配置");
        return channelInfoDto;
    }

    /**
     * 查询当前访客已经有进行中的会话
     *
     * @param visitorInfo
     * @return
     */
    private VisitorConversationInfoRespVO queryConversationIfExist(VisitorInfoDTO visitorInfo, ChannelInfoDTO channelInfo) {
        List<ConversationDTO> conversationDTOList = conversationService.queryConversationListBy(visitorInfo.getAccount(), ConversationStateEnum.NORMAL, 1);
        if (CollectionUtils.isEmpty(conversationDTOList)) {
            return null;
        }
        ConversationDTO existConversation = conversationDTOList.get(0);
        AtomicLong csId = new AtomicLong();
        Account csAccount = existConversation.getRoomMemberList().stream()
                .filter(rm -> StringUtils.isNotBlank(rm.getExt()))
                .filter(rm -> {
                    JSONObject ext = JsonUtil.toObject(rm.getExt(), JSONObject.class);
                    if (ext == null || !"1".equals(ext.getString("userType"))) {
                        return false;
                    }
                    csId.set(ext.getLong("csId"));
                    return true;
                }).findFirst().orElse(null);
        if (csAccount == null) {
            return null;
        }

        SkillSetConfig skillSetConfig = getSkillSetConfig(existConversation);

        List<CsMemberInfoDTO> csMemberInfoDtoList = aiCustomerServiceClient.getNormalListBy(Lists.newArrayList(csId.get()));
        if (skillSetConfig == null || CollectionUtils.isEmpty(csMemberInfoDtoList)) {
            // 客服账号被冻结
            closingConversationService.closeConversation(existConversation.getId(), csId.get());
            return null;
        }
        return VisitorConversationInfoRespVO.buildBoundConversationSuccess(existConversation, csAccount, csMemberInfoDtoList.get(0), channelInfo, skillSetConfig);
    }

    /**
     * 获取技能组配置信息
     *
     * @param conversation
     * @return
     */
    private SkillSetConfig getSkillSetConfig(ConversationDTO conversation) {
        JSONObject conversationExt = JsonUtil.toObject(conversation.getExt(), JSONObject.class);
        if (conversationExt == null) {
            log.warn("存在的会话无扩展信息，cId:{}", conversation.getId());
            return null;
        }
        Long skillSetId = conversationExt.getLong("skillSetId");
        SkillSetConfig skillSetConfig = skillSetConfigService.queryBySkillSetId(skillSetId);
        if (skillSetConfig == null) {
            log.warn("存在的会话id的技能组配置已失效，cId:{}，skillSetId:{}", conversation.getId(), skillSetId);
            return null;
        }
        return skillSetConfig;
    }

    /**
     * 创建会话
     *
     * @param channelInfo
     * @param visitorInfo
     * @return
     */
    private CreatingConversationRespDTO createConversation(ChannelInfoDTO channelInfo, VisitorInfoDTO visitorInfo) {
        Map<String, String> conversationExt = Maps.newHashMap();
        conversationExt.put("tenantId", visitorInfo.getTenantId());
        conversationExt.put("channelId", channelInfo.getId());
        conversationExt.put("channelName", channelInfo.getName());
        conversationExt.put("routeId", String.valueOf(channelInfo.getRouteId()));
        Optional.ofNullable(channelInfo.getAppearanceType()).ifPresent(appearanceType -> {
            conversationExt.put("appearanceType", appearanceType.toString());
        });
        String conversationExtStr = JsonUtil.toJson(conversationExt);

        try {
            CreatingConversationReqDTO reqDTO = CreatingConversationReqDTO.builder()
                    .accountId(visitorInfo.getAccount().getId())
                    .conversationExt(conversationExtStr)
                    .build();
            log.info("创建会话参数：{}", reqDTO);
            Stopwatch stopwatch = Stopwatch.createStarted();
            CreatingConversationRespDTO respDTO = conversationService.create(reqDTO);
            stopwatch.stop();
            log.info("创建会话结果：{}，耗时：{}ms", respDTO, stopwatch.elapsed(TimeUnit.MILLISECONDS));
            return respDTO;
        } catch (Exception e) {
            log.error("创建会话异常！accountUserId:{}", imAccountService.buildUserImUserId(visitorInfo), e);
            return null;
        }

    }

    /**
     * 创建路由分发任务
     *
     * @param visitorInfo
     * @param channelInfo
     * @param conversation
     */
    private CreatingRouteTaskRespDTO createRouteTask(VisitorInfoDTO visitorInfo, ChannelInfoDTO channelInfo, CreatingConversationRespDTO conversation) {
        CreatingRouteTaskDTO creatingRouteTaskDTO = CreatingRouteTaskDTO.builder()
                .tenantId(visitorInfo.getTenantId())
                .channelId(channelInfo.getId())
                .channelUserId(visitorInfo.getUserId())
                .conversationId(conversation.getConversationId())
                .routeId(channelInfo.getRouteId())
                .accountId(visitorInfo.getAccount().getId().toString())
                .build();
        try {
            log.info("创建路由分发任务参数：{}", creatingRouteTaskDTO);
            Stopwatch stopwatch = Stopwatch.createStarted();
            CreatingRouteTaskRespDTO routeResultDTO = routeTaskService.createTask(creatingRouteTaskDTO);
            stopwatch.stop();
            log.info("创建路由分发任务结果：{}，耗时：{}ms", routeResultDTO, stopwatch.elapsed(TimeUnit.MILLISECONDS));
            return routeResultDTO;
        } catch (BizException e) {
            return CreatingRouteTaskRespDTO.of(null, e.getMessage(), RouteTaskStatusEnum.FAIL);
        } catch (Exception e) {
            log.error("创建路由分发任务异常！conversationId:{}", conversation.getConversationId(), e);
            return null;
        }
    }

    /**
     * 会话绑定客服和访客的房间
     *
     * @param skillSet
     * @param resultDTO
     */
    private BindResultDTO bindRoom(SkillSet skillSet, RouteResultDTO resultDTO, Map<String, Account> accountMap) {
        // 当前会话信息
        Conversation conversation = conversationService.getById(resultDTO.getRouteTaskDTO().getConversationId());
        ServiceAssert.notNull(conversation, StatusCode.Common.NULL_ERROR.getCode(), "会话不存在");
        if (conversation.getState().equals(ConversationStateEnum.END)) {
            log.warn("会话已结束，conversationId:{}", conversation.getId());
            return null;
        }

        JSONObject conversationExt = Optional.ofNullable(JsonUtil.toObject(conversation.getExt(), JSONObject.class)).orElseGet(JSONObject::new);
        conversationExt.put("skillSetId", String.valueOf(skillSet.getId()));
        conversationExt.put("skillSetName", skillSet.getName());
        conversationExt.put("csId", resultDTO.getCsInfo().getUserId());
        conversation.setExt(JsonUtil.toJson(conversationExt));
        conversation.setStartTime(DateUtil.now());
        conversation.setUpdateTime(DateUtil.now());

        List<Long> accountIdList = accountMap.values().stream().map(Account::getId).distinct().collect(Collectors.toList());
        try {
            log.info("会话绑定房间参数：conversationId:{},accountIdList:{}", conversation.getId(), accountIdList);
            Stopwatch stopwatch = Stopwatch.createStarted();
            BindResultDTO bindResultDTO = conversationService.bindRoom(conversation, accountIdList);
            stopwatch.stop();
            log.info("会话绑定房间结果：{}，耗时：{}ms", bindResultDTO, stopwatch.elapsed(TimeUnit.MILLISECONDS));
            return bindResultDTO;
        } catch (Exception e) {
            log.error("会话绑定房间异常！conversationId:{},accountIdList:{}", conversation.getId(), accountIdList, e);
            return null;
        }
    }

    /**
     * 记录访客今日转人工次数
     *
     * @param visitorImAccountId
     */
    private void recordVisitorAccessCsCount(Long visitorImAccountId) {
        String redisKey = String.format(CacheKeyUtil.CsrRoute.VISITOR_ACCESS_CS_COUNT, visitorImAccountId);
        redissonClient.getAtomicLong(redisKey).incrementAndGet();
        Date currentTime = new Date();
        redissonClient.getAtomicLong(redisKey).expire(Duration.ofMillis(DateUtil.endOfDay(currentTime).getTime() - currentTime.getTime()));
    }

    /**
     * 记录客服今日接待访客次数
     *
     * @param csId
     */
    private void recordCsAccessVisitorCount(Long csId) {
        String redisKey = String.format(CacheKeyUtil.CsrRoute.CS_ACCESS_VISITOR_COUNT, csId);
        redissonClient.getAtomicLong(redisKey).incrementAndGet();
        Date currentTime = new Date();
        redissonClient.getAtomicLong(redisKey).expire(Duration.ofMillis(DateUtil.endOfDay(currentTime).getTime() - currentTime.getTime()));
    }

    /**
     * 推送新会话通知到客服工作台
     *
     * @param room
     * @param csAccount
     * @param accountMap
     */
    private void pushNewConversationEventNotify(Room room, Account csAccount, Map<String, Account> accountMap) {
        Map<Long, Account> aMap = accountMap.values().stream().collect(Collectors.toMap(Account::getId, v -> v, (o, n) -> o));
        conversationService.pushNewConversationEvent(csAccount, room, aMap);
    }

    /**
     * 推送会话接入系统消息到客服工作台
     *
     * @param csAccount
     * @param visitorAccount
     * @param conversation
     * @param skillSetConfig
     */
    private void pushCreateConversationSystemMsg(Account csAccount, Account visitorAccount, Conversation conversation, SkillSetConfig skillSetConfig, CsMemberInfoDTO csInfo) {
        // 与前端约定，系统消息扩展字段带有 systemMsgType=2 的，表示会话接入
        JSONObject systemMsgExt = new JSONObject();
        systemMsgExt.put("systemMsgType", "2");
        setCsNameAndHeadImg(systemMsgExt, conversation, skillSetConfig, csInfo);
        String systemMsgExtStr = systemMsgExt.toJSONString();

        String todayStr = DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN) + "&nbsp;&nbsp;&nbsp;";

        List<SendSystemMessageDTO> sendSystemMessageList = new ArrayList<>();
        sendSystemMessageList.add(SendSystemMessageDTO.of(visitorAccount.getAccountUserId(), conversation.getId(), skillSetConfig.getGreetingsTip(), systemMsgExtStr, conversation.getRoomId()));
        sendSystemMessageList.add(SendSystemMessageDTO.of(csAccount.getAccountUserId(), conversation.getId(), todayStr + visitorAccount.getAccountName() + "接入会话，请及时接待", systemMsgExtStr, conversation.getRoomId()));

        Map<String, Account> aMap = new HashMap<>();
        aMap.put(visitorAccount.getAccountUserId(), visitorAccount);
        aMap.put(csAccount.getAccountUserId(), csAccount);
        messageService.sendSystemMsg(sendSystemMessageList, aMap);
    }

    private void setCsNameAndHeadImg(JSONObject systemMsgExt, Conversation conversation,
                                     SkillSetConfig skillSetConfig, CsMemberInfoDTO csInfo) {
        String csNickName = StringUtils.EMPTY;
        String csHeadImg = StringUtils.EMPTY;
        Integer appearanceType = null;
        JSONObject conversationExt = JsonUtil.toObject(conversation.getExt(), JSONObject.class);
        if (conversationExt != null) {
            appearanceType = conversationExt.getInteger("appearanceType");
        }
        if (appearanceType != null) {
            if (appearanceType == 1) {
                csNickName = skillSetConfig.getCsNickname();
                csHeadImg = skillSetConfig.getCsHeadImg();
            } else {
                csNickName = csInfo.getNickName();
                csHeadImg = csInfo.getHeadImg();
            }
        } else {
            csNickName = csInfo.getNickName();
            csHeadImg = csInfo.getHeadImg();
        }

        systemMsgExt.put("csName", csNickName);
        systemMsgExt.put("csHeadImg", csHeadImg);
    }

}
